/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
 *
 */

import fs from 'fs';
import path from 'path';
import process from 'process';

import { DEFAULT_HVIGOR_CONFIG_JSON_FILE_NAME } from '../../common/const/const.js';
import { HVIGOR_PROJECT_WRAPPER_HOME, HVIGOR_USER_HOME } from '../../common/const/path-const.js';
import { HvigorConfigLoader } from '../../common/util/hvigor-config-loader.js';
import { Json5Reader } from './json5-reader.js';

export interface Environment {
  nodeHome?: string;
  hvigorUserHome?: string;
}

export interface Execution {
  daemon?: boolean;
  parallel?: boolean;
  incremental?: boolean;
  typeCheck?: boolean;
  analyze?: string | boolean;
  skipNativeIncremental?: boolean;
  optimizationStrategy?: string;
}

export interface Debugging {
  stacktrace?: boolean;
}

export interface Logging {
  level?: string;
}

export interface NodeOptions {
  maxOldSpaceSize?: number | undefined; // 设置老生代的最大内存
  maxSemiSpaceSize?: number | undefined; // 设置新生代半区的最大内存
  exposeGC?: boolean;
}

// NodeOption值的来源
export interface NodeOptionSrc {
  argv: number | undefined;
  config: number | undefined;
  execArgv: number | undefined;
  default: number;
}

export interface JavaOptions {
  Xmx?: number;
}

// NodeOptions默认值
export const defaultOptions: Required<NodeOptions> = {
  maxOldSpaceSize: 8192, // 设置默认值为8192
  maxSemiSpaceSize: 16, // 设置默认16
  exposeGC: true,
};

export interface HvigorConfig {
  modelVersion: string;
  dependencies: Record<string, string>;
  environment?: Environment;
  execution?: Execution;
  logging?: Logging;
  debugging?: Debugging;
  nodeOptions?: NodeOptions;
  javaOptions?: JavaOptions;
  properties?: Record<string, any>;
}

/**
 * hvigor-config.json5读取器
 *
 * @since 2023/04/06
 */
export class HvigorConfigReader extends Json5Reader {
  private static readonly maxOldSpaceSizeParamPrefiex = '--max-old-space-size=';
  private static readonly maxSemiSpaceSizeParamPrefiex = '--max-semi-space-size=';

  public static getHvigorConfig(): HvigorConfig | undefined {
    const hvigorConfigPath = path.resolve(HVIGOR_PROJECT_WRAPPER_HOME, DEFAULT_HVIGOR_CONFIG_JSON_FILE_NAME);
    if (!fs.existsSync(hvigorConfigPath)) {
      return undefined;
    }
    const projrctJson5Obj = this.getJson5Obj(hvigorConfigPath);
    const userHomeConfigFile = path.resolve(HVIGOR_USER_HOME, DEFAULT_HVIGOR_CONFIG_JSON_FILE_NAME);
    let userHomeJson5Obj;
    if (fs.existsSync(userHomeConfigFile)) {
      userHomeJson5Obj = this.getJson5Obj(userHomeConfigFile);

      // 工程目录下优先，相同名字后面覆盖前面
      projrctJson5Obj.properties = { ...userHomeJson5Obj.properties, ...projrctJson5Obj.properties };
    }
    return projrctJson5Obj;
  }

  public static getPropertiesConfigValue<T>(key: string): T | undefined {
    return HvigorConfigLoader.getInstance().getPropertiesConfigValue<T>(key);
  }

  public static getMaxOldSpaceSize(trigByConfigChange=false): number {
    // hvigorw --max-old-space-size=123 传入的参数
    const maxOldSpaceSizeArgvStr = process.argv.find((arg) => arg.startsWith(this.maxOldSpaceSizeParamPrefiex));
    const maxOldSpaceSizeArgv = Number(maxOldSpaceSizeArgvStr?.slice(maxOldSpaceSizeArgvStr?.indexOf('=') + 1) ?? '');

    // hvigor-config.json5 中填写的参数
    const maxOldSpaceSizeConfig = HvigorConfigReader.getHvigorConfig()?.nodeOptions?.maxOldSpaceSize;

    // node --max-old-space-size=123 hvigorw.js 传入的参数，可通过修改 bat 或者 shell 脚本传入该参数
    const maxOldSpaceSizeExecArgvStr = process.execArgv.find((arg) => arg.startsWith(this.maxOldSpaceSizeParamPrefiex));
    const maxOldSpaceSizeExecArgv = Number(maxOldSpaceSizeExecArgvStr?.slice(maxOldSpaceSizeExecArgvStr?.indexOf('=') + 1) ?? '');

    // 默认值
    const maxOldSpaceSizeDefault = defaultOptions.maxOldSpaceSize;

    const maxOldSpaceSrc: NodeOptionSrc = {
      argv: maxOldSpaceSizeArgv,
      config: maxOldSpaceSizeConfig,
      execArgv: maxOldSpaceSizeExecArgv,
      default: maxOldSpaceSizeDefault
    };

    return this.getPriorVal(maxOldSpaceSrc, trigByConfigChange);
  }

  public static getMaxSemiSpaceSize(trigByConfigChange=false): number {
    // hvigorw --max-semi-space-size=123 传入的参数
    const maxSemiSpaceSizeArgvStr = process.argv.find((arg) => arg.startsWith(this.maxSemiSpaceSizeParamPrefiex));
    const maxSemiSpaceSizeArgv = Number(maxSemiSpaceSizeArgvStr?.slice(maxSemiSpaceSizeArgvStr?.indexOf('=') + 1) ?? '');

    // hvigor-config.json5 中填写的参数
    const maxSemiSpaceSizeConfig = HvigorConfigReader.getHvigorConfig()?.nodeOptions?.maxSemiSpaceSize;

    // node --max-semi-space-size=123 hvigorw.js 传入的参数，可通过修改 bat 或者 shell 脚本传入该参数
    const maxSemiSpaceSizeExecArgvStr = process.execArgv.find((arg) => arg.startsWith(this.maxSemiSpaceSizeParamPrefiex));
    const maxSemiSpaceSizeExecArgv = Number(maxSemiSpaceSizeExecArgvStr?.slice(maxSemiSpaceSizeExecArgvStr?.indexOf('=') + 1) ?? '');

    // 默认值
    const maxSemiSpaceSizeDefault = defaultOptions.maxSemiSpaceSize;

    const maxSemiSpaceSizeSrc: NodeOptionSrc = {
      argv: maxSemiSpaceSizeArgv,
      config: maxSemiSpaceSizeConfig,
      execArgv: maxSemiSpaceSizeExecArgv,
      default: maxSemiSpaceSizeDefault
    };

    return this.getPriorVal(maxSemiSpaceSizeSrc, trigByConfigChange);
  }

  public static getNodeParamFromProcessArgv(): string[] {
    const nodeParamsFromProcessArgv: string[] = [];
    for (const paramNamePrefix of [this.maxOldSpaceSizeParamPrefiex, this.maxSemiSpaceSizeParamPrefiex]) {
      const nodeParamWithValue = process.argv.find((arg) => arg.startsWith(paramNamePrefix));
      if (nodeParamWithValue) {
        nodeParamsFromProcessArgv.push(nodeParamWithValue);
      }
    }
    return nodeParamsFromProcessArgv;
  }

  /**
   * 优先级顺序：命令行参数 > hvigor-config中的参数 > hvigorw的.bat或者shell文件 > 默认值
   * 如果是检测到config配置改变导致的重启，则优先级： hvigor-config中的参数 > 默认值
   * 之所以进行判断，处理场景为第一次通过daemon启动后，第二次编译前注释掉config文件下的参数，默认值不会生效的问题。
   * 不做判断的话，当参数写入execArgv，重启不会使默认值生效
   * @param src 来源
   * @param trigByConfigChange 配置变化触发标志，如果为true，则只返回src.config或src.default中的一个
   * @return 返回src中的一个值
   */
  private static getPriorVal(src: NodeOptionSrc, trigByConfigChange: boolean): number {
    if (trigByConfigChange) {
      return src.config || src.default;
    }
    return src.argv || src.config || src.execArgv || src.default;
  }
}
